/* 
   Copyright 2012 Ryne Okimoto

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

package manager;

import recipe.RecipeData;

import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.imageio.ImageIO;

/**
 * Class - FileManager
 * 
 * Description - Class to perform file management functions
 * @author Ryne Okimoto
 *
 */
public class FileManager {
	// Class data
	private static final String BEGIN        = "#BEG#";
	private static final String END          = "#END#";
	private static final String NAME         = "#NAM#";
	private static final String TAG          = "#TAG#";
	private static final String CATEGORY     = "#CAT#";
	private static final String IMAGE        = "#IMG#";
	
	private static final String INGREDIENTS   = "ING#";
	private static final String DIRECTIONS    = "DIR#";
	private static final String RECIPE        = "REC#";
	
	private static final String RECIPE_PATH  = "./rcp_db/Rcp_Files/";
	private static final String IMAGE_PATH   = "./rcp_db/Img_Files/";
	
	private RecipeData recipeObject;
	private File recipeFile;
	private String fileName;
	private String imageName;
	private BufferedReader rcpReader;
	private BufferedWriter rcpWriter;
	private BufferedImage rcpImage;
	
	/**
	 * Default constructor
	 */
	public FileManager(){
		recipeFile = null;
		recipeFile = null;
		fileName = null;
		imageName = null;
		rcpReader = null;
		rcpWriter = null;
		rcpImage = null;
	} // constructor
	
	/**
	 * Overloaded constructor
	 * 
	 * Description: Constructor that accepts a filename to read/write data
	 * @param fileName
	 */
	public FileManager(String fileName){
		this.fileName = RECIPE_PATH + fileName;
		recipeObject = new RecipeData();
		rcpReader = null;
		rcpWriter = null;
		rcpImage = null;
		openFile();
		readFile();
		
		imageName = getImageFromRecipe(recipeObject);
	} // overloaded constructor
	
	/**
	 * Overloaded constructor
	 * @param fileName
	 * @param recipeFile
	 */
	public FileManager(String fileName, File recipeFile){
		this.fileName = RECIPE_PATH + fileName;
		this.recipeFile = recipeFile;
		recipeObject = new RecipeData();
		rcpWriter = null;
		rcpReader = null;
		rcpImage = null;
		
		readFile();
		
		imageName = getImageFromRecipe(recipeObject);
	}
	
	/**
	 * Method - getDefaultRecipePath
	 * Description - Getter for the default directory path for the recipe files
	 * @return String - the recipe path
	 */
	public static String getDefaultRecipePath(){
		return RECIPE_PATH;
	}
	
	/**
	 * Method - getDefaultImagePath
	 * Description - Getter for the default directory path for the image files
	 * @return String - the image path
	 */
	public static String getDefaultImagePath(){
		return IMAGE_PATH;
	}
	
	/**
	 * Method - resetFile
	 * Description - Resets the file by deleting the old one and
	 * creating a new one
	 * @param fn
	 * @return True - Returns if the file reset is successful
	 * @return False - Returns if the file reset fails
	 */
	public boolean resetFile(String fn){
		boolean isFileChanged = false;
		isFileChanged = recipeFile.delete();
		
		fileName = RECIPE_PATH + fn;
		isFileChanged = openFile();
		
		return isFileChanged;
	}
	
	/**
	 * Method - getImageFromRecipe
	 * Description - Sets the path for the image using the
	 *               image file name from the recipe object
	 * @param rcp
	 * @return String
	 */
	private String getImageFromRecipe(RecipeData rcp){
		String image = rcp.getImageFileName();
		String imagePath = "";
		
		if(!image.isEmpty()){
			imagePath = IMAGE_PATH + image;
		}
		
		return imagePath;
	}
	
	/**
	 * Method - openImage
	 * Description - Checks for the existence of the image file and reads it into a BufferedImage
	 * @return BufferedImage
	 */
	public BufferedImage openImage(){
		
		if((imageName!= null && !imageName.isEmpty()) && 
			Files.exists(Paths.get(imageName))){
			try{
				rcpImage = ImageIO.read(new File(imageName));
			}
			catch(IOException io){
				io.getMessage();
				io.printStackTrace();
			}
		}
		
		return rcpImage;
	}
	
	/**
	 * Method - openFile
	 * Description - Method to set the File pointer with the given filename 
	 * @return boolean
	 * @throws IOException
	 */
	public boolean openFile(){
		boolean readIsSuccessful = false;
		try
		{
			// Try to open file
			this.recipeFile = new File(fileName);
			this.rcpReader = new BufferedReader(new FileReader(recipeFile));
			readIsSuccessful = true;
		}
		catch(FileNotFoundException fnf)
		{
			// If file does not exist, then create it
			try{
				BufferedWriter createFile = new BufferedWriter(new FileWriter(new File(fileName)));
				createFile.write("");
				createFile.close();
				
				this.rcpReader = new BufferedReader(new FileReader(recipeFile));
			}
			catch(IOException io){
				io.getMessage();
				io.printStackTrace();
				System.out.println("Error: Unable to create new recipe file.");
			}
		}
		
		return readIsSuccessful;
	} // openFile
	
	/**
	 * Method - readFile
	 * Description - Method to read data from file and store into a RecipeData object
	 * @return boolean
	 */
	public boolean readFile(){
		boolean readOK = false;
		
		String lineOfFile = null;
		
		try{
			
			if(rcpReader == null){
				rcpReader = new BufferedReader(new FileReader(recipeFile));
			}
			
			lineOfFile = rcpReader.readLine();
			
			while(lineOfFile != null && !lineOfFile.startsWith(END + RECIPE)){
				// Read each line of the .rcp file until there are no more to read
				// or the end recipe tag is found
				
				// Check the line tag
				if(lineOfFile.startsWith(BEGIN + RECIPE)){
					lineOfFile = rcpReader.readLine();
				}
				
				if(lineOfFile.startsWith(NAME)){
					recipeObject.setRecipeName(lineOfFile.substring(5));
				}
				else if(lineOfFile.startsWith(CATEGORY)){
					recipeObject.setCategory(lineOfFile.substring(5));
				}
				else if(lineOfFile.startsWith(IMAGE)){
					recipeObject.setImageFileName((lineOfFile.substring(5)));
				}
				else if(lineOfFile.startsWith(TAG)){
					recipeObject.setTags(lineOfFile.substring(5));
				}
				else if(lineOfFile.startsWith(BEGIN + INGREDIENTS)){
					recipeObject.setListOfIngredients(readIngredients(lineOfFile, rcpReader));
				}
				else if(lineOfFile.startsWith(BEGIN + DIRECTIONS)){
					recipeObject.setInstructions(readDirections(lineOfFile, rcpReader));
				}
				
				lineOfFile = rcpReader.readLine();
				
			} // while
			
			//rcpReader.close();
			
			readOK = true;
		} // try
		catch(FileNotFoundException fnf){
			fnf.printStackTrace();
			System.out.println(fnf.getMessage());
		}
		catch(IOException io){
			io.printStackTrace();
			System.out.println(io.getMessage());
		}
		
		return readOK;
	} // readFile
	
	/**
	 * Method - readIngredients
	 * 
	 * Description - Method to read all the ingredients from a file into a string
	 * @param line
	 * @param reader
	 * @return String
	 */
	private String readIngredients(String line, BufferedReader reader){
		StringBuilder ingredientsList = new StringBuilder();
		
		try{
			// Read the line after the begin ingredients tag
			line = reader.readLine();
			
			while(!line.startsWith(END + INGREDIENTS)){
				ingredientsList.append(line + "\n");
				
				line = reader.readLine();
			} // while
			
		}
		catch(IOException io){
			io.getMessage();
			io.printStackTrace();
		}
		
		return ingredientsList.toString();
	} // readIngredients
	
	/**
	 * Method - readDirections
	 * Description - Reads the directions from the .rcp file
	 * @param line
	 * @param reader
	 * @return String
	 */
	private String readDirections(String line, BufferedReader reader){
		StringBuilder directions = new StringBuilder();
		
		try{
			// Read the line after the begin directions tag
			line = reader.readLine();
			
			while(!line.startsWith(END + DIRECTIONS)){
				directions.append(line + "\n");
				
				line = reader.readLine();
			} // while
		}
		catch(IOException io){
			io.getMessage();
			io.printStackTrace();
		}
		
		return directions.toString();
		
	} // readDirections
	
	/**
	 * getData
	 * Description: Returns the RecipeData object from the file
	 * @return RecipeData
	 */
	public RecipeData getData()
	{
		return recipeObject;
	}
	
	/**
	 * Method - createRecipe
	 * Description - Assumes that the FileManager contains a instantiated
	 * RecipeData object. Initializes values for the recipe and
	 * saves the file to disk.
	 * @param recipeName
	 * @param category
	 * @param ingredients
	 * @param instructions
	 * @return boolean
	 */
	public boolean createRecipe
		(String recipeName, String category, String ingredients, 
		String instructions, String image, String tags){
		
		// Recipe object is instantiated
		// Simply set data fields
		recipeObject.setRecipeName(recipeName);
		recipeObject.setCategory(category);
		recipeObject.setListOfIngredients(ingredients);
		recipeObject.setInstructions(instructions);
		recipeObject.setImageFileName(image);
		recipeObject.setTags(tags);
		
		return closeFile();
	}
	
	/**
	 * Method - closeFile
	 * Description - Saves the recipe data to the File pointer and closes the file.
	 * @return boolean
	 */
	public boolean closeFile(){
		boolean closeIsSuccessful = false;
		
		try
		{
			writeData();
			closeIsSuccessful = true;
		}
		catch(IOException io)
		{
			io.getMessage();
			io.printStackTrace();
		}
		
		return closeIsSuccessful;
	}
	
	/**
	 * Method - writeData
	 * Description - Method to generate a String to write to file
	 * @return void
	 */
	private void writeData() throws IOException {
		rcpWriter = new BufferedWriter(new FileWriter(recipeFile));
		
		rcpWriter.write(generateTextData());
		
		// Close reader and writer
		rcpWriter.close();
		rcpReader.close();
	}
	
	/**
	 * Method - generateTextData
	 * Description - Method to convert the RecipeData object into a String
	 *               that can be written to a file
	 * @return String
	 */
	private String generateTextData(){
		StringBuilder textData = new StringBuilder();
		
		// Begin recipe tag
		textData.append(generateRecipeTag(true));
		
		// Name tag
		textData.append(generateName());
		
		// Category tag
		textData.append(generateCategory());
		
		// Image tag
		textData.append(generateImage());
		
		// Tags tag
		textData.append(generateTags());
		
		// Begin ingredients tag
		textData.append(generateIngredientsTag(true));
		// Add ingredients
		textData.append(generateIngredients());
		// End ingredients tag
		textData.append(generateIngredientsTag(false));
		
		// Begin directions tag
		textData.append(generateDirectionsTag(true));
		textData.append(generateDirections());
		textData.append(generateDirectionsTag(false));
		
		// End recipe tag
		textData.append(generateRecipeTag(false));
		
		return textData.toString();
	}
	
	/**
	 * Method - generateRecipeTag
	 * Description - Returns the begin recipe tag
	 * @param isBegin
	 * @return String
	 */
	private String generateRecipeTag(boolean isBegin){
		if(isBegin){
			return BEGIN + RECIPE + "\n";
		}
		else{
			return END + RECIPE + "\n";
		}
	}
	
	/**
	 * Method - generateName
	 * Description - Returns the name tag and recipe name
	 * @return String
	 */
	private String generateName(){
		return NAME + recipeObject.getRecipeName() + "\n";
	}
	
	/**
	 * Method - generateCategory
	 * Description - Returns the category tag and category name
	 * @return String
	 */
	private String generateCategory(){
		return CATEGORY + recipeObject.getCategory() + "\n";
	}
	
	/**
	 * Method - generateImage
	 * Description - Returns the image tag and image filename
	 * @return String
	 */
	private String generateImage(){
		return IMAGE + recipeObject.getImageFileName() + "\n";
	}
	
	/**
	 * Method - generateTags
	 * Description - Returns the tags tag and list of tags
	 * @return String
	 */
	private String generateTags(){
		return TAG + recipeObject.getTags() + "\n";
	}
	
	/**
	 * Method - generateIngredientsTag
	 * Description - Returns the begin/end tag and ingredients tag
	 * @return String
	 */
	private String generateIngredientsTag(boolean isBegin){
		if(isBegin){
			return BEGIN + INGREDIENTS + "\n";
		}
		else{
			return END + INGREDIENTS + "\n";
		}
	}
	
	/**
	 * Method - generateIngredients
	 * Description - Returns the ingredients
	 * @return String
	 */
	private String generateIngredients(){
		return recipeObject.getListOfIngredientsAsString() + "\n";
	}
	
	/**
	 * Method - generateDirectionsTag
	 * Description - Returns the begin tag and directions tag
	 * @param isBegin
	 * @return String
	 */
	private String generateDirectionsTag(boolean isBegin){
		if(isBegin){
			return BEGIN + DIRECTIONS + "\n";
		}
		else{
			return END + DIRECTIONS + "\n";
		}
	}
	
	/**
	 * Method - printData
	 * Description - Prints the RecipeData object data to the console
	 * @return void
	 */
	private void printData(){
		System.out.print(
			"Recipe name: " + recipeObject.getRecipeName() + "\n" +
			"Category: " + recipeObject.getCategory() + "\n" +
			"Image: " + recipeObject.getImageFileName() + "\n" +
			"Tags: " + recipeObject.getTags() + "\n" +
			"Ingredients: " + recipeObject.getListOfIngredients() + "\n" +
			"Directions: " + recipeObject.getInstructions() + "\n\n"
		);
	}
	
	/**
	 * Method - createString
	 * Description - Creates a string containing the recipe
	 * @return String
	 */
	public String createString(){
		StringBuilder dataAsString = new StringBuilder();
		dataAsString.append(
			"Recipe name: " + recipeObject.getRecipeName() + "\n" +
			"Category: " + recipeObject.getCategory() + "\n" +
			"Image: " + recipeObject.getImageFileName() + "\n" +
			"Tags: " + recipeObject.getTags() + "\n" +
			"Ingredients: \n" + recipeObject.getListOfIngredientsAsString() + "\n" +
			"Directions: \n" + recipeObject.getInstructions()
			);
		
		return dataAsString.toString();
	}
	
	/**
	 * Method - generateDirections
	 * Description - Returns the directions
	 * @return String
	 */
	private String generateDirections(){
		return recipeObject.getInstructions() + "\n";
	}
	
	/**
	 * Unit tester
	 * @param args
	 */
	public static void main(String[] args){
		
		char testToRun = 2;
		String testFileName = "testRead.rcp";
		
		// Create RecipeData object
		System.out.println("Creating RecipeData object...");
		RecipeData testRecipe = new RecipeData();
		
		// Create FileManager
		System.out.println("Creating FileManager object...");
		FileManager fm = new FileManager();
		
		
		switch(testToRun){
		case 1:
			// Create file and write
			
			// Set fields for RecipeData object
			testRecipe.setCategory("<Category here>");
			testRecipe.setImageFileName("<image filename here>");
			testRecipe.setIngredients("<ingredients list here>");
			testRecipe.setInstructions("<instructions list here>");
			testRecipe.setRecipeName("Test recipe");
			testRecipe.setTags("<tags list here>");
			
			// Set object to manager
			fm.recipeObject = testRecipe;
			// Write data to file
			System.out.println("Writing RecipeData object to file...");
			fm.closeFile();
			
			// Delete file
			// fm.recipeFile.delete();
			break;
			
		case 2:
			// Read from file and print data
			fm = new FileManager(testFileName);
			
			if(fm.readFile()){
				fm.printData();
			}
			else{
				System.out.println("Error: File not read successfully. Unable to print data.");
			}
			break;
			
		default:
			System.out.println("Error: Select a test.");
			break;
		} // switch
		
		System.out.println("\nEnd of unit test\n");
	}
} // class
